import { mat4, vec3 } from "wgpu-matrix";
import {
    cubeVertexArray,
    cubeVertexSize,
    cubeUVOffset,
    cubePositionOffset,
    cubeVertexCount,
} from '../meshes/cube';
import cubeWgsl from "../shaders/cube.wgsl?raw";
import { OrthographicCamera } from "../camera/OrthographicCamera";
import { InputSystem } from "../io/InputSystem";

const canvas = document.querySelector('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.oncontextmenu = () => false;

const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const context = canvas.getContext("webgpu");
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();

context.configure({
    device,
    format: presentationFormat,
    usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
});

const vertexBuffer = device.createBuffer({
    size: cubeVertexArray.byteLength,
    usage: GPUBufferUsage.VERTEX,
    mappedAtCreation: true
});
new Float32Array(vertexBuffer.getMappedRange()).set(cubeVertexArray);
vertexBuffer.unmap();

const pipeline = device.createRenderPipeline({
    layout: "auto",
    vertex: {
        module: device.createShaderModule({ code: cubeWgsl }),
        buffers: [
            {
                arrayStride: cubeVertexSize,
                attributes: [
                    {
                        shaderLocation: 0,
                        offset: cubePositionOffset,
                        format: "float32x4"
                    },
                    {
                        shaderLocation: 1,
                        offset: cubeUVOffset,
                        format: "float32x2"
                    }
                ]
            }
        ]
    },
    fragment: {
        module: device.createShaderModule({ code: cubeWgsl }),
        targets: [
            {
                format: presentationFormat
            }
        ]
    },
    primitive: {
        topology: "triangle-list",
        cullMode: "back",
    },
    depthStencil: {
        depthWriteEnabled: true,
        depthCompare: "less",
        format: "depth24plus"
    }
});

const depthTexture = device.createTexture({
    size: [canvas.width, canvas.height],
    format: "depth24plus",
    usage: GPUTextureUsage.RENDER_ATTACHMENT
});

const uniformBufferSize = 4 * 16;
const uniformBuffer = device.createBuffer({
    size: uniformBufferSize,
    usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
});

async function loadTexture(url) {

    const response = await fetch(url);
    const imageBitmap = await createImageBitmap(await response.blob());

    const texture = device.createTexture({
        size: [imageBitmap.width, imageBitmap.height, 1],
        format: "rgba8unorm",
        usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
    });

    device.queue.copyExternalImageToTexture(
        { source: imageBitmap },
        { texture: texture },
        [imageBitmap.width, imageBitmap.height]
    );

    return texture;

}

const cubeTexture = await loadTexture("./textures/yangjun.jpg");

const sampler = device.createSampler({ magFilter: "linear", minFilter: "linear" });

const uniformBindGroup = device.createBindGroup({
    layout: pipeline.getBindGroupLayout(0),
    entries: [
        {
            binding: 0,
            resource: { buffer: uniformBuffer }
        },
        {
            binding: 1,
            resource: sampler,
        },
        {
            binding: 2,
            resource: cubeTexture.createView()
        }
    ]
});

const renderPassDescriptor: GPURenderPassDescriptor = {
    colorAttachments: [
        {
            view: undefined,
            clearValue: [0.5, 0.5, 0.5, 1.0],
            loadOp: "clear",
            storeOp: "store"
        }
    ],
    depthStencilAttachment: {
        view: depthTexture.createView(),
        depthClearValue: 1,
        depthLoadOp: "clear",
        depthStoreOp: "store"
    }
};

const modelViewProjectionMatrix = mat4.create();
const modelMatrix = mat4.identity();
const modelViewMatrix = mat4.create();

const camera = new OrthographicCamera();

const inputSystem = new InputSystem();
inputSystem.initCanvas(canvas);

function updateModelViewProjectionMatrix(delta: number) {

    camera.update(inputSystem);

    mat4.multiply(camera.view, modelMatrix, modelViewMatrix);

    mat4.multiply(camera.projectionMatrix, modelViewMatrix, modelViewProjectionMatrix);

}

let lastFrameMS = Date.now();
function render() {

    const now = Date.now();
    const deltaTime = (now - lastFrameMS) / 1000;
    lastFrameMS = now;

    updateModelViewProjectionMatrix(deltaTime);

    device.queue.writeBuffer(
        uniformBuffer,
        0,
        modelViewProjectionMatrix.buffer,
        modelViewProjectionMatrix.byteOffset,
        modelViewProjectionMatrix.byteLength
    );

    renderPassDescriptor.colorAttachments[0].view = context.getCurrentTexture().createView();

    const commandEncoder = device.createCommandEncoder();
    const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
    passEncoder.setPipeline(pipeline);
    passEncoder.setBindGroup(0, uniformBindGroup);
    passEncoder.setVertexBuffer(0, vertexBuffer);
    passEncoder.draw(cubeVertexCount);
    passEncoder.end();
    device.queue.submit([commandEncoder.finish()]);

}

function frame() {

    render();

    requestAnimationFrame(frame);

}

frame();